use std::{iter::Peekable, str::Chars};

use crate::{token::Delimiter, Token};

const END_CHARS: &str = ")]}:;|";
const WORD_PREMITTED_PUNCTS: &str = "?!_+-*/&^%$@.<>=~";

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Invalid Ident \"{0}\"")]
    InvalidIdentifier(String),
    #[error("Invalid Char \"'{0}'\"")]
    InvalidChar(char),
    #[error("Invalid Digit {0}")]
    InvalidDigit(char),
    #[error("Unclosed string literal")]
    UnclosedString,
    #[error("Integer overflow")]
    Overflow,
}

pub struct Lexer<'src> {
    iter: Peekable<Chars<'src>>,
}

impl<'src> Lexer<'src> {
    pub fn new(src: &'src str) -> Self {
        Self {
            iter: src.chars().peekable(),
        }
    }

    pub fn next_token(&mut self) -> super::Result<Token> {
        while let Some(first) = self.iter.next() {
            // skip whitespace
            if first.is_whitespace() {
                continue;
            }

            match first {
                ';' => self.skip_comment(),
                '(' => return Ok(Token::Begin(Delimiter::Parenthesis)),
                '[' => return Ok(Token::Begin(Delimiter::Bracket)),
                '{' => return Ok(Token::Begin(Delimiter::Brace)),
                ')' => return Ok(Token::End(Delimiter::Parenthesis)),
                ']' => return Ok(Token::End(Delimiter::Bracket)),
                '}' => return Ok(Token::End(Delimiter::Brace)),
                '\'' => return Ok(Token::SingleQuote),
                '|' => return self.eat_params(),
                ':' => return Ok(Token::Colon),
                '"' => return self.eat_string(),
                '-' => return self.eat_number(first),
                '#' => return self.eat_boolean(),
                '.' => return self.assemble_real(0),
                ch if ch.is_ascii_digit() => {
                    return self.eat_number(first);
                }
                _ => return self.eat_word(first).map(Token::Word),
            }
        }

        Ok(Token::EOF)
    }

    fn eat_number(&mut self, first: char) -> super::Result<Token> {
        if first == '0' {
            if let Some(second) = self.iter.peek() {
                if second.is_whitespace() || END_CHARS.contains(*second) {
                    return Ok(Token::Integer(0));
                }
                match self.iter.next().unwrap().to_ascii_lowercase() {
                    'b' => return self.eat_non_decimal_integer(2),
                    'o' => return self.eat_non_decimal_integer(8),
                    'x' => return self.eat_non_decimal_integer(16),
                    '.' => return self.assemble_real(0),
                    ch if ch.is_ascii_digit() => return self.eat_decimal(ch),
                    ch => return Err(Error::InvalidDigit(ch).into()),
                }
            }
            return Ok(Token::Integer(0));
        }
        self.eat_decimal(first)
    }

    fn eat_non_decimal_integer(&mut self, radix: u32) -> super::Result<Token> {
        let mut ans = 0u32;
        while let Some(ch) = self.iter.peek() {
            if ch.is_whitespace() || END_CHARS.contains(*ch) {
                break;
            }

            let digit_char = self.iter.next().unwrap();

            if let Some(digit) = digit_char.to_digit(radix) {
                if let Some(checked) = ans.checked_mul(radix).and_then(|v| v.checked_add(digit)) {
                    ans = checked;
                } else {
                    return Err(Error::Overflow.into());
                }
            } else {
                
                return Err(Error::InvalidDigit(digit_char).into());
            }
        }
        Ok(Token::Integer(unsafe { std::mem::transmute(ans) }))
    }

    fn eat_decimal(&mut self, first: char) -> super::Result<Token> {
        if first == '-' {
            if let Some(second) = self.iter.peek() {
                if second.is_whitespace() || END_CHARS.contains(*second) {
                    return Ok(Token::Word(first.to_string()));
                }

                let first = self.iter.next().unwrap();

                return match self.eat_positive_decimal(first)? {
                    Token::Integer(int) => Ok(Token::Integer(-int)),
                    Token::Real(real) => Ok(Token::Real(-real)),
                    _ => panic!("Unreachable code!"),
                };
            }
            return Ok(Token::Word(first.to_string()));
        }
        self.eat_positive_decimal(first)
    }

    fn eat_positive_decimal(&mut self, first: char) -> super::Result<Token> {
        if let Some(mut ans) = first.to_digit(10) {
            while let Some(ch) = self.iter.peek() {
                if ch.is_whitespace() || END_CHARS.contains(*ch) {
                    return Ok(Token::Integer(ans as i32));
                }

                let ch = self.iter.next().unwrap();
                if ch == '.' {
                    return self.assemble_real(ans);
                } else if let Some(digit) = ch.to_digit(10) {
                    if let Some(checked) = ans.checked_mul(10).and_then(|v| v.checked_add(digit)) {
                        ans = checked;
                    } else {
                        return Err(Error::Overflow.into());
                    }
                } else {
                    return Err(Error::InvalidDigit(ch).into());
                }
            }
            return Ok(Token::Integer(ans as i32));
        }
        Err(Error::InvalidDigit(first).into())
    }

    fn assemble_real(&mut self, integer: u32) -> super::Result<Token> {
        let mut len = 0;
        let mut float: u32 = 0;
        while let Some(ch) = self.iter.peek() {
            if ch.is_whitespace() || END_CHARS.contains(*ch) {
                break;
            }

            let digit_char = self.iter.next().unwrap();

            if let Some(digit) = digit_char.to_digit(10) {
                if let Some(checked) = float.checked_mul(10).and_then(|v| v.checked_add(digit)) {
                    float = checked;
                    len += 1;
                } else {
                    return Err(Error::Overflow.into());
                }
            } else {
                return Err(Error::InvalidDigit(digit_char).into());
            }
        }
        if len == 0 {
            return Err(Error::InvalidIdentifier(format!("{integer}.")).into());
        }
        Ok(Token::Real(
            integer as f32 + float as f32 / 10u32.pow(len) as f32,
        ))
    }

    fn eat_string(&mut self) -> super::Result<Token> {
        let mut buffer = String::new();
        while let Some(ch) = self.iter.next() {
            match ch {
                '"' => return Ok(Token::String(buffer)),
                '\\' => buffer.push(self.eat_control_char()?),
                _ => buffer.push(ch),
            }
        }
        Err(Error::UnclosedString.into())
    }

    fn eat_control_char(&mut self) -> super::Result<char> {
        if let Some(ch) = self.iter.next() {
            match ch {
                '0' => Ok('\0'),
                '"' => Ok('\"'),
                '\\' => Ok('\\'),
                'n' => Ok('\n'),
                't' => Ok('\t'),
                'r' => Ok('\r'),
                _ => Err(Error::InvalidChar(ch).into()),
            }
        } else {
            Err(Error::UnclosedString.into())
        }
    }

    fn eat_word(&mut self, first: char) -> super::Result<String> {
        let mut buffer = String::from(first);
        while let Some(ch) = self.iter.peek() {
            if ch.is_whitespace() || END_CHARS.contains(*ch) {
                break;
            }
            let ch = self.iter.next().unwrap();
            if ch.is_alphanumeric() || WORD_PREMITTED_PUNCTS.contains(ch) {
                buffer.push(ch)
            } else {
                buffer.push(ch);
                return Err(Error::InvalidIdentifier(buffer).into());
            }
        }
        Ok(buffer)
    }

    fn skip_comment(&mut self) {
        // while let Some(ch) = self.iter.next() {
        //     if ch == '\n' {
        //         break;
        //     }
        // }

        for ch in self.iter.by_ref() {
            if ch == '\n' {
                break;
            }
        }
    }

    fn eat_boolean(&mut self) -> super::Result<Token> {
        match self.iter.next() {
            Some('t') => Ok(Token::Boolean(true)),
            Some('f') => Ok(Token::Boolean(false)),
            _ => Err(Error::InvalidChar('#').into()),
        }
    }

    fn eat_params(&mut self) -> super::Result<Token> {
        let mut params = vec![];
        while let Some(ch) = self.iter.next() {
            if ch == '|' {
                break;
            } else if ch.is_whitespace() {
                continue;
            }
            params.push(self.eat_word(ch)?);
        }
        params.reverse();
        Ok(Token::Params(params))
    }
}

#[cfg(test)]
mod test {
    use std::{
        fs::File,
        io::Read,
        path::{Path, PathBuf},
    };

    use crate::{comptime::lexer::Lexer, Token};

    #[test]
    fn lexer_eat_whitespace() {
        let mut lex = Lexer::new("  ");
        assert!(matches!(lex.next_token(), Ok(Token::EOF)))
    }

    #[test]
    fn lexer_eat_number() {
        let mut lex = Lexer::new("123.456 123)");
        assert!(
            matches!(lex.next_token(), Ok(Token::Real(f)) if (f - 123.456).abs() < f32::EPSILON)
        );
        assert!(matches!(lex.next_token(), Ok(Token::Integer(123))));

        let mut lex = Lexer::new("-123 -123.456 123.");
        assert!(matches!(lex.next_token(), Ok(Token::Integer(-123))));
        assert!(
            matches!(lex.next_token(), Ok(Token::Real(f)) if (f + 123.456).abs() < f32::EPSILON)
        );

        let mut lex = Lexer::new("0x66ccff 0b110111 1-1");
        assert!(matches!(lex.next_token(), Ok(Token::Integer(0x66ccff))));
        assert!(matches!(lex.next_token(), Ok(Token::Integer(0b110111))));
    }

    #[test]
    fn lexer_eat_string() {
        let mut lex = Lexer::new("\"string\" \"stri\ng\" \"stri\\ng\" \"");
        assert!(matches!(lex.next_token(), Ok(Token::String(_))));
        assert!(matches!(lex.next_token(), Ok(Token::String(_))));
        assert!(matches!(lex.next_token(), Ok(Token::String(_))));
    }

    #[test]
    fn lexer_eat_word() {
        let mut lex = Lexer::new("hel10 wor1d _");
        loop {
            match lex.next_token() {
                Ok(Token::EOF) => break,
                tok => assert!(matches!(tok, Ok(Token::Word(_)))),
            }
        }

        let mut lex = Lexer::new("+ - * / ? !");
        loop {
            match lex.next_token() {
                Ok(Token::EOF) => break,
                tok => assert!(matches!(tok, Ok(Token::Word(_)))),
            }
        }
    }

    #[test]
    fn nested() {
        let mut lex = Lexer::new("(println (+ \"Hello, world!\" 233 ))");
        loop {
            match lex.next_token() {
                Ok(Token::EOF) => break,
                tok => println!("{tok:?}"),
            }
        }
    }

    #[test]
    fn read_all_examples() {
        for file in enumerate_files().unwrap() {
            let src = read_file(file.clone()).unwrap();
            let mut lex = Lexer::new(&src);

            loop {
                if let Token::EOF = lex.next_token().unwrap() {
                    break;
                }
            }
        }
    }

    fn enumerate_files() -> std::io::Result<impl Iterator<Item = PathBuf>> {
        let cur = std::env::current_dir()?.join("examples");

        Ok(std::fs::read_dir(cur)?
            .filter(|entry| {
                if let Ok(entry) = entry {
                    return entry.path().extension().unwrap() == "dj";
                }
                false
            })
            .map(|entry| entry.unwrap().path()))
    }

    fn read_file<P: AsRef<Path>>(path: P) -> std::io::Result<String> {
        let mut src_file = File::open(path)?;
        let mut src = String::new();
        src_file.read_to_string(&mut src)?;
        Ok(src)
    }
}
